// ... 源码 有缩减

//代码注释：https://blog.csdn.net/dongcehao/article/details/106987886


function sameVnode (a, b) {
  return (
    a.key === b.key && ( // 如果我们 没有加 key, 默认是 undifined
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

// 用于 比较 新老节点的不同，然后更新的 函数
return function patch (oldVnode, vnode, hydrating, removeOnly) {
  // 当新节点不存在的时候，销毁旧节点
  if (isUndef(vnode)) {
    if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
    return
  }

  let isInitialPatch = false
  // 用来存储 insert 钩子函数，在 插入节点之后调用
  const insertedVnodeQueue = []

  // 如果旧节点 是未定义的，直接创建新节点
  if (isUndef(oldVnode)) {
    // empty mount (likely as component), create new root element
    isInitialPatch = true
    createElm(vnode, insertedVnodeQueue)
  } else {
    const isRealElement = isDef(oldVnode.nodeType)
    // 当老节点不是真实的 dom 节点， 当两个节点是相同节点的时候，进入 patctVnode 的过程
    // 而 patchVnode 也是 传说中 diff updateChildren 的调用者
    if (!isRealElement && sameVnode(oldVnode, vnode)) {
      // patch existing root node
      patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
    } else {
      // 当老节点是真实存在的 dom 节点的时候
      if (isRealElement) {
        // mounting to a real element
        // check if this is server-rendered content and if we can perform
        // a successful hydration.
        // 当 老节点是 真实节点，而是在 ssr 环境的时候，修改 SSR_ATTR 属性
        if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
          oldVnode.removeAttribute(SSR_ATTR)
          hydrating = true
        }
        if (isTrue(hydrating)) {
          if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
            invokeInsertHook(vnode, insertedVnodeQueue, true)
            return oldVnode
          } else if (process.env.NODE_ENV !== 'production') {
            warn(
              'The client-side rendered virtual DOM tree is not matching ' +
              'server-rendered content. This is likely caused by incorrect ' +
              'HTML markup, for example nesting block-level elements inside ' +
              '<p>, or missing <tbody>. Bailing hydration and performing ' +
              'full client-side render.'
            )
          }
        }
        // either not server-rendered, or hydration failed.
        // create an empty node and replace it
        // 设置 oldVnode 为一个包含 oldVnode 的无属性节点
        oldVnode = emptyNodeAt(oldVnode)
      }

      // replacing existing element
      const oldElm = oldVnode.elm
      // 获取父亲节点，这样方便 删除或者增加节点
      const parentElm = nodeOps.parentNode(oldElm)

      // create new node
      // 递归 更新父占位符元素
      // 就是执行一遍 父节点的 destory 和 create 、insert 的 钩子函数
      // 类似于 style 组件，事件组件，这些 钩子函数
      createElm(
        vnode,
        insertedVnodeQueue,
        // extremely rare edge case: do not insert if old element is in a
        // leaving transition. Only happens when combining transition +
        // keep-alive + HOCs. (#4590)
        oldElm._leaveCb ? null : parentElm,
        nodeOps.nextSibling(oldElm)
      )

      // update parent placeholder node element, recursively
      if (isDef(vnode.parent)) {
        let ancestor = vnode.parent
        const patchable = isPatchable(vnode)
        while (ancestor) {
          for (let i = 0; i < cbs.destroy.length; ++i) {
            cbs.destroy[i](ancestor)
          }
          ancestor.elm = vnode.elm
          if (patchable) {
            for (let i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](emptyNode, ancestor)
            }
            // #6513
            // invoke insert hooks that may have been merged by create hooks.
            // e.g. for directives that uses the "inserted" hook.
            const insert = ancestor.data.hook.insert
            if (insert.merged) {
              // start at index 1 to avoid re-invoking component mounted hook
              for (let i = 1; i < insert.fns.length; i++) {
                insert.fns[i]()
              }
            }
          } else {
            registerRef(ancestor)
          }
          ancestor = ancestor.parent
        }
      }

      // destroy old node
      // 销毁老节点
      if (isDef(parentElm)) {
        removeVnodes([oldVnode], 0, 0)
      } else if (isDef(oldVnode.tag)) {
        // 触发老节点 的 destory 钩子
        invokeDestroyHook(oldVnode)
      }
    }
  }

  // 执行 虚拟 dom 的 insert 钩子函数
  invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  // 返回最新 vnode 的 elm ，也就是真实的 dom节点
  return vnode.elm
}

// ...